package ltd.flython.dlock.redis;

import ltd.flython.dlock.context.HostIdentitySupplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Component
public class RedisLockHelper {

    //记录本地锁持有者
    private Map<String,String> lockHolder = new ConcurrentHashMap<>();

    final private WeakHashMap<String,RedisLock> locks = new WeakHashMap<>();
    final private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();

    @Autowired
    RedisTemplate<String,String> redisTemplate;

    @Autowired
    HostIdentitySupplier hostIdentitySupplier;

    TaskScheduler scheduler;
    {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(3);
        taskScheduler.setThreadNamePrefix("RedisLockBiz-");
        taskScheduler.initialize();
        scheduler = taskScheduler;
    }

    public RedisLock getLock(String name){
        //保证单线程写弱字典
        rw.writeLock().lock();
        try{
            return locks.computeIfAbsent(name,(n)->new RedisLock(this, n));
        } finally {
            rw.writeLock().unlock();
        }
    }

    public void notifyLock(String lockName){
        RedisLock lock = null;
        rw.readLock().lock();
        try{
            lock = locks.get(lockName);
        } finally {
            rw.readLock().unlock();
        }
        if (lock != null){
            lock.notifyHead();
        }
    }


    public <T> T executeLua(Class<T> clazz, String luaScript, List<String> keys, Object... args) {
        DefaultRedisScript<T> script = new DefaultRedisScript<>(luaScript, clazz);
        //经测试这里的参数只能传integer，可能跟spring的long的序列化器有关
        return this.redisTemplate.execute(script, keys, args);
    }

    public String currentThreadId(){
       String instanceId = hostIdentitySupplier.get();
       long threadId = Thread.currentThread().getId();
       return instanceId + ":" + threadId;
    }


    public void scheduleRenewExpire(RedisLock lock, int leaseMs, String threadId) {
        lockHolder.put(lock.getName(),threadId);
        doScheduleRenewExpire(lock,leaseMs,threadId);
    }

    private void doScheduleRenewExpire(RedisLock lock, int leaseMs, String threadId) {
        if (!threadId.equals(lockHolder.get(lock.getName()))){
            //锁已经不归threadId线程所有
            return;
        }
        boolean success = lock.doRenewExpire(leaseMs, threadId);
        if (success){
            scheduler.schedule(()-> doScheduleRenewExpire(lock,leaseMs,threadId), Instant.now().plusMillis(leaseMs - (leaseMs >> 2)));
        }
    }

    public void stopRenewExpireTask(String name) {
        lockHolder.remove(name);
    }

    public String getHolderId(String name) {
        return lockHolder.get(name);
    }
}
